const Koa = require('koa');
const Router = require('koa-router');
const koaBody = require('koa-body');
const fs = require('fs');
const fsPromise = fs.promises;
const compressing = require('compressing');
const session = require('koa-session');
const app = new Koa();
const router = new Router();
const config = require('./config');
const tool = require('./tool');
const compress = require('koa-compress');
const https = require('https');
const http = require('http');
const iniDic = require('./iniDic');

app.keys = ['45ac4dc7-f9b3-48f8-ad4e-6d2b59a50da5'];

app.use(session({
    key: 'koa:sess',   // cookie key (default is koa:sess)
    maxAge: 86400000,  // cookie的过期时间 maxAge in ms (default is 1 days)
    overwrite: true,  // 是否可以overwrite    (默认default true)
    httpOnly: true, // cookie是否只有服务器端可以访问 httpOnly or not (default true)
    signed: true,   // 签名默认true
    rolling: false,  // 在每次请求时强行设置cookie，这将重置cookie过期时间（默认：false）
    renew: false,  // (boolean) renew session when session is nearly expired,
}, app));

app.use(koaBody({
    multipart: true,
    maxFieldsSize: 50 * 1024 * 1024,
    formidable: {
        uploadDir: __dirname + '/upload'
    }
}));

app.use(compress({
    threshold: 2048
}));

// 启动服务
router.post('/api/start_cluster', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var clusterId = ctx.request.body.cluster_id;

    var runningList = await tool.getRunningList();
    if (runningList.length > 0) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '已存在正在启动或者已启动的服务，启动失败。'
        });
        return;
    }

    tool.runByClusterId(clusterId);
    ctx.response.body = JSON.stringify({
        code: 0,
        msg: '已提交启动指令。'
    });
});

// 停止服务
router.post('/api/stop_cluster', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var runningList = await tool.getRunningList();
    var pid = [];
    for (let e of runningList) {
        if (e.clusterId == ctx.request.body.cluster_id) {
            pid.push(e.pid);
        }
    }
    if (pid.length) {
        if (!(await tool.stopByClusterId(ctx.request.body.cluster_id))) {
            await tool.stopByPid(pid);
        }
        ctx.response.body = JSON.stringify({
            code: 0,
            msg: '操作成功。'
        })
    } else {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '操作失败。'
        })
    }
});

// 获取日志的接口
router.post('/api/load_clusterlog', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var clusterId = ctx.request.body.cluster_id;
    var masterLog = '';
    var cavesLog = '';

    try {
        masterLog = await fsPromise.readFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/log-master.log', { encoding: 'utf-8' });
        cavesLog = await fsPromise.readFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/log-caves.log', { encoding: 'utf-8' });
    } catch (e) {

    }

    if (ctx.request.body.masterLine != undefined && /\d+/.test(ctx.request.body.masterLine)) {
        var tmpArr = masterLog.split(/\r?\n/);
        masterLog = tmpArr.slice(ctx.request.body.masterLine).join('\n');
    }
    if (ctx.request.body.cavesLine != undefined && /\d+/.test(ctx.request.body.cavesLine)) {
        var tmpArr = cavesLog.split(/\r?\n/);
        cavesLog = tmpArr.slice(ctx.request.body.cavesLine).join('\n');
    }

    ctx.response.body = JSON.stringify({
        code: 0,
        data: 'success',
        data: {
            masterLog: masterLog,
            cavesLog: cavesLog
        }
    });
});

// 获取cluster.ini
router.post('/api/get_clusterini', async (ctx) => {
    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var clusterId = ctx.request.body.cluster_id;
    var clusterIni = await tool.getClusterIniByClusterId(clusterId);
    var clusterToken = await fsPromise.readFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/cluster_token.txt', { encoding: 'utf-8' });

    ctx.response.body = JSON.stringify({
        code: 0,
        data: Object.assign({
            BASE: {
                cluster_dir_name: {
                    zh: '存档名称',
                    type: 'text',
                    default: clusterId
                },
                cluster_token: {
                    zh: '饥荒服务端令牌',
                    type: 'text',
                    default: clusterToken
                }
            }
        }, clusterIni)
    });
});

// 获取存档的接口
router.get('/api/cluster_table.json', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    // console.log(global.clusterMapService);

    var clusterIds = await fsPromise.readdir(config.dotkleiPath + '/DoNotStarveTogether');
    var runningList = await tool.getRunningList();
    var clusterInis = [];
    var ret = []
    for (let clusterId of clusterIds) {
        try {
            var content = await fsPromise.readFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/cluster.ini', {
                encoding: 'utf-8'
            });
            clusterInis.push(content);
        } catch (e) {
            console.log(e);
        }
    }
    for (let i = 0; i < clusterInis.length; i++) {
        let ini = clusterInis[i];
        let iniItemTemp = ini.split(/\r?\n/);
        let iniItem = {
            id: clusterIds[i],
            runningStatus: false,
        };
        // 运行状态给赋进去
        for (let e of runningList) {
            if (e.clusterId == clusterIds[i]) {
                iniItem.runningStatus = true;
            }
        }
        for (let iniItemBefore of iniItemTemp) {
            if (iniItemBefore.indexOf('=') > -1) {
                let iniItemSpec = iniItemBefore.split('=');
                iniItem[iniItemSpec[0].trim()] = iniItemSpec[1].trim();
            }
        }
        ret.push(iniItem);
    }
    ctx.response.body = JSON.stringify({
        code: 0,
        msg: "success",
        count: 0,
        data: ret
    });
});

// 新建存档的接口
router.post('/api/add_cluster', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var clusterData = ctx.request.body;

    if (/[./\\]/.test(clusterData['BASE////==////cluster_dir_name'])) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '操作失败',
        });
        return;
    }

    if ('' == clusterData['BASE////==////cluster_dir_name']) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '请输入存档存档名称。'
        });
        return;
    }

    var clusterIds = await fsPromise.readdir(config.dotkleiPath + '/DoNotStarveTogether');
    var clusterId = clusterData['BASE////==////cluster_dir_name'];
    var clusterToken = clusterData['BASE////==////cluster_token'];

    if (clusterIds.indexOf(clusterId) > -1) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '当前存档已存在。'
        });
        return;
    }


    // 创建主目录
    console.log('==========MKDIR==========')
    await fsPromise.mkdir(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId);

    // 创建主配置文件
    var fileContent = tool.buildClusterIni(clusterData);
    console.log('==========MKCLUSTERINI==========');
    await fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/cluster.ini', fileContent);

    // 创建cluster_token.txt
    console.log('==========MKCLUSTERTOKEN==========');
    await fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/cluster_token.txt', clusterToken);

    // 创建地表路径
    console.log('==========MKMASTERDIR==========');
    await fsPromise.mkdir(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/Master');

    // 创建洞穴路径
    console.log('==========MKCAVESDIR==========');
    await fsPromise.mkdir(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/Caves');

    // 创建地表服务启动脚本
    console.log('==========MKSTARTMASTERSH==========');
    fileContent = await fsPromise.readFile(__dirname + '/configtpl/startmaster', { encoding: 'utf-8' });
    fileContent = fileContent.replace('{{CLUSTER_ID}}', clusterId);
    await fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/startmaster.sh', fileContent);
    fsPromise.chmod(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/startmaster.sh', 0o777); // 权限

    // 创建洞穴服务启动脚本
    console.log('==========MKSTARTCAVESSH==========');
    fileContent = await fsPromise.readFile(__dirname + '/configtpl/startcaves', { encoding: 'utf-8' });
    fileContent = fileContent.replace('{{CLUSTER_ID}}', clusterId);
    await fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/startcaves.sh', fileContent);
    fsPromise.chmod(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/startcaves.sh', 0o777); // 权限

    // 创建地表server.ini
    console.log('==========MKMASTERSERVERINI==========');
    fileContent = await fsPromise.readFile('./configtpl/master_serverini', { encoding: 'utf-8' });
    await fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/Master/server.ini', fileContent);

    // 创建洞穴server.ini
    console.log('==========MKCAVESSERVERINI==========');
    fileContent = await fsPromise.readFile('./configtpl/caves_serverini', { encoding: 'utf-8' });
    await fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/Caves/server.ini', fileContent);

    // 创建地表世界生成默认配置
    await tool.saveLevelDataOverride({
        'BASE////==////cluster_dir_name': clusterId,
        'BASE////==////type': 'Master',
    });
    
    // 创建洞穴世界生成默认配置
    await tool.saveLevelDataOverride({
        'BASE////==////cluster_dir_name': clusterId,
        'BASE////==////type': 'Caves',
    });

    // 创建地表和洞穴的默认MOD配置
    await tool.saveClusterMods(clusterId, []);

    ctx.response.body = JSON.stringify({
        code: 0,
        msg: '操作成功。'
    });
});

// 保存存档配置
router.post('/api/save_clusterini', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var clusterData = ctx.request.body;
    var clusterId = clusterData['BASE////==////cluster_dir_name'];
    var clusterToken = clusterData['BASE////==////cluster_token'];

    // 创建主配置文件
    var fileContent = tool.buildClusterIni(clusterData);
    console.log('==========MKCLUSTERINI==========');
    await fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/cluster.ini', fileContent);

    // 创建cluster_token.txt
    console.log('==========MKCLUSTERTOKEN==========');
    await fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/cluster_token.txt', clusterToken);

    ctx.response.body = JSON.stringify({
        code: 0,
        msg: '操作成功。'
    });
});

// 删除存档
router.post('/api/delete_cluster', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    if (/[./\\]/.test(ctx.request.body.id)) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '操作失败',
        });
        return;
    }

    try {
        fsPromise.rmdir(config.dotkleiPath + '/DoNotStarveTogether/' + ctx.request.body.id, {
            recursive: true
        });
        ctx.response.body = JSON.stringify({
            code: 0,
            msg: '操作成功',
        });
    } catch (e) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '操作失败',
        });
    }
});

router.post('/api/upload_cluster', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var file = ctx.request.files.file;

    try {
        console.log("====================unzip=====================")
        await compressing.zip.uncompress(file.path, __dirname + '/upload/unziptemp');
        console.log("====================copy Master=====================");
        var dirs = await fsPromise.readdir(__dirname + '/upload/unziptemp');
        var dir = "";
        if (dirs.indexOf('Master') == -1) {
            if (dirs.length == 1) {
                dir = '/' + dirs[0];
                dirs = await fsPromise.readdir(__dirname + '/upload/unziptemp/' + dirs[0]);
                if (dirs.indexOf('Master') == -1) {
                    ctx.response.body = JSON.stringify({
                        code: 1,
                        msg: '无法解析的目录结构'
                    });
                    return;
                }
            } else {
                ctx.response.body = JSON.stringify({
                    code: 1,
                    msg: '无法解析的目录结构'
                });
                return;
            }
        }

        await tool.recursiveCopy(__dirname + '/upload/unziptemp' + dir + '/Master', config.dotkleiPath + '/DoNotStarveTogether/' + ctx.request.body.cluster_id + '/Master');
        console.log("====================copy Caves=====================");
        await tool.recursiveCopy(__dirname + '/upload/unziptemp' + dir + '/Caves', config.dotkleiPath + '/DoNotStarveTogether/' + ctx.request.body.cluster_id + '/Caves');
        console.log("====================rm unziptemp=====================");
        await fsPromise.rmdir(__dirname + '/upload/unziptemp', { recursive: true });
        console.log("====================rm zip=====================");
        await fsPromise.unlink(file.path);
    } catch (e) {
        console.log(e);
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: "解压发生未知错误。"
        });
        return;
    }

    ctx.response.body = JSON.stringify({
        code: 0,
        msg: "操作成功"
    })
});

// 获取mod列表
router.get('/api/mod_table.json', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var modList = await tool.getAllModInfo();
    ctx.response.body = JSON.stringify({
        code: 0,
        msg: 'success.',
        data: modList
    });
});

// 获取mod的下载地址
router.post('/api/get_mod_down_info', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var modId = ctx.request.body.mod_id;
    if (!/\d+/.test(modId)) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'error request'
        });
        return;
    }

    var modDownInfo = await tool.getModDownInfo(modId);
    ctx.response.body = JSON.stringify({
        code: 0,
        data: modDownInfo
    });
});


// 发指令给服务端下载mod保存到对应路径
router.post('/api/download_mod', async (ctx) => {
    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var modId = ctx.request.body.mod_id;
    if (!/^\d+$/.test(modId)) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '请输入数字的ID号'
        });
        return;
    }

    // 先看下MOD是否存在
    var dirs = await fsPromise.readdir(config.modsPath);
    if ('false' === ctx.request.body.force && dirs.indexOf('workshop-' + modId) !== -1) {
        ctx.response.body = JSON.stringify({
            code: -1,
            msg: '该MOD已存在，是否重新下载该MOD？'
        });
        return;
    }
    
    if (dirs.indexOf('workshop-' + modId) !== -1) {
        await fsPromise.rmdir(config.modsPath + 'workshop-' + modId, {
            recursive: true
        });
    }

    /**
     * {
     *  code - 200 成功, 404 无mod
     *  url - 下载地址
     *  msg - 信息
     * }
     */
    var modDownInfo = await tool.getModDownInfo(modId);
    

    if (modDownInfo.code == 404) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '未找到该MOD，请重新确认MODID。'
        });
        return;
    } else if (modDownInfo.code != 200) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: modDownInfo.msg
        });
        return;
    }

    if (!modDownInfo.url) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '该MOD未提供下载服务。'
        });
        return;
    }

    var client = http;
    if (modDownInfo.url.indexOf('https://') == 0) {
        client = https;
    }
    
    var dest = './upload/workshop-' + modId;
    console.log('==========下载MOD[' + modId + ']==========');
    await new Promise((resolve, reject) => {
        const file = fs.createWriteStream(dest);
        console.log('下载[' + modDownInfo.url + ']')
        client.get(modDownInfo.url, (res) => {
            if (res.statusCode !== 200) {
                reject(res.statusCode);
                return;
            }

            res.on('end', () => {

            });

            // 进度、超时等
            file.on('finish', () => {
                file.close(resolve);
            }).on('error', (err) => {
                fs.unlink(dest);
                reject(err.message);
            });
            res.pipe(file);
        });
    });

    console.log('==========解压MOD[' + modId + ']==========');
    await compressing.zip.uncompress(dest, config.modsPath + '/workshop-' + modId);
    console.log('==========删除MODZIP[' + modId + ']==========');
    await fsPromise.unlink(dest);

    ctx.response.body = JSON.stringify({
        code: 0,
        msg: '下载成功',
        data: {
            mod_id: modId
        }
    });
});

// 获取人员名单列表
router.post('/api/get_namelist', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var nameType = ctx.request.body.type;
    var clusterId = ctx.request.body.cluster_id;
    if (['blacklist', 'whitelist', 'adminlist'].indexOf(nameType) == -1) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'error request'
        });
        return;
    }

    var content = ''
    try {
        content = await fsPromise.readFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/' + nameType + '.txt', { encoding:  'utf-8' });
    } catch (e) {
        if (e.code !== 'ENOENT') { // 如果文件并不是不存在的话把异常给出去
            ctx.response.body = JSON.stringify({
                code: 1,
                msg: '未知错误',
                data: e
            });
            return;
        }
    }
    ctx.response.body = JSON.stringify({
        code: 0,
        msg: 'success',
        data: content
    });
});

// 保存人员名单列表
router.post('/api/save_namelist', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var nameType = ctx.request.body.type;
    var clusterId = ctx.request.body.cluster_id;
    var content = ctx.request.body.content;
    if (['blacklist', 'whitelist', 'adminlist'].indexOf(nameType) == -1) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'error request'
        });
        return;
    }

    try {
        await fsPromise.writeFile(config.dotkleiPath + '/DoNotStarveTogether/' + clusterId + '/' + nameType + '.txt', content);
    } catch (e) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '未知错误',
            data: e
        });
        return;
    }
    ctx.response.body = JSON.stringify({
        code: 0,
        msg: '保存成功。',
    });
});

// 获取字典
router.get('/api/get_dic', async (ctx) =>{

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    ctx.response.body = JSON.stringify({
        code: 0,
        data: iniDic
    });
});

// 获取地表/洞穴配置
router.post('/api/get_leveldataoverride', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    ctx.response.body = JSON.stringify(await tool.getLevelDataOverride(ctx.request.body.cluster_id, ctx.request.body.type));
});

// 保存地表/洞穴配置
router.post('/api/save_leveldataoverride', async (ctx) => {
    
    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    ctx.response.body = JSON.stringify(await tool.saveLevelDataOverride(ctx.request.body));
});

// 获取存档的mod配置
router.post('/api/get_cluster_mods', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    ctx.response.body = JSON.stringify(await tool.getClusterMods(ctx.request.body.cluster_id));

});

// 保存存档mod配置，目前不做里面子配置
router.post('/api/save_cluster_mods', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    var clusterId = ctx.request.body.cluster_id;

    ctx.response.body = JSON.stringify(await tool.saveClusterMods(clusterId, JSON.parse(ctx.request.body.data)));

});

// 发送指令
router.post('/api/send_cmd', async (ctx) => {

    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    ctx.response.body = JSON.stringify(tool.sendCmd(ctx.request.body.cluster_id, ctx.request.body.cmd_str));
});

// 获取服务器信息
router.post('/api/get_server_info', async (ctx) => {
    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    ctx.response.body = JSON.stringify(await tool.getServerInfo());
});

router.post('/api/check_and_update', async (ctx) => {
    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    ctx.response.body = JSON.stringify(tool.checkAndUpdate());
});

router.post('/api/check_and_update_result', async (ctx) => {
    if (!ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
        return;
    }

    ctx.response.body = JSON.stringify(tool.checkAndUpdateResult());
});

// 登录
router.post('/api/login', async (ctx) => {
    if (ctx.request.body.un == config.manageUser && ctx.request.body.pw == config.managePass) {
        ctx.session.logined = true;
        ctx.response.body = JSON.stringify({
            code: 0,
            msg: '登录成功。'
        });
    } else {
        ctx.session.logined = false;
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: '用户名或密码错误。'
        });
    }
});

// 检查登录
router.get('/api/check_login', async (ctx) => {
    if (ctx.session.logined) {
        ctx.response.body = JSON.stringify({
            code: 0,
            msg: 'success'
        });
    } else {
        ctx.response.body = JSON.stringify({
            code: 1,
            msg: 'not login'
        });
    }
});

// 登出
router.get('/api/logout', async (ctx) => {
    ctx.session.logined = false;
    ctx.response.body = JSON.stringify({
        code: 0,
        msg: 'success'
    });
})

app.use(router.routes());
app.use(require('koa-static')(__dirname + '/static'));


app.listen(3000);
